package com.distribute.config.distributeLock;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

@Component
@Aspect
@Slf4j
public class DistributedLockAspect {

    @Resource
    DistributedLockConfiguration distributedLockConfiguration;


    private final int MAX_RETRY_TIMES = 2;

    @Around("@annotation(distributedLock)")
    public Object lock(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        String spELString = distributedLock.key();
        String key = generateKeyBySpEL(spELString, joinPoint);
        long expiration = distributedLock.expiration();
        long timeout = distributedLock.timeout();
        String token = UUID.randomUUID().toString();
        int retryTimes = 0;
        while (retryTimes <= MAX_RETRY_TIMES) {
            log.info("start acquire distribute lock, [{}] retry,", retryTimes + 1);
            boolean locked = distributedLockConfiguration.acquireLock(key, token, expiration, timeout, scheduledExecutorService);
            if (locked) {
                try {
                    return joinPoint.proceed();
                } finally {
                    distributedLockConfiguration.releaseLock(key, token, scheduledExecutorService);
                }
            } else {
                retryTimes++;
                log.info("acquire lock failed, after sleep [{}]ms then acquire again", expiration);
                Thread.sleep(expiration);
            }
        }
        throw new Exception("Failed to acquire lock for key: " + key);
    }

    private String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
        if (StringUtils.isBlank(spELString)) {
            return "";
        }
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        SpelExpressionParser parser = new SpelExpressionParser();

        Expression expression = parser.parseExpression(spELString);//设置解析上下文(有哪些占位符，以及每种占位符的值)
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i],args[i]);
        }
        return expression.getValue(context).toString();
    }
}
